<?php
namespace my;

/**
 * 微信扫码支付类
* @author dev
* @method __construct 初始化
* @method pay 发起支付
* @method notice 接收通知
* @method filt_Param 设置生成加密串时要过滤的参数
*/
class Code
{
    private $config,$puclicdata;

    /**
     *
     * @param array $config 开发者信息
     *          string $config['appid'] 开发者编号
     *          string $config['secret'] 开发者密钥
     *          string $config['mch_id'] 微信支付商户号
     *          string $config['md5key'] 微信支付密钥
     *          string $config['notify_url'] 页面通知地址
     */
    function __construct($config=array())
    {
        $this->config=array(
            'appid'             =>  "wxb74a96c4e765e867", #公众号APPID
            'secret'            =>  "fda38936bfb38042907c6d57841a8fdb", ##公众号secret
            'mch_id'            =>  "1483145362", #商户平台商户号
            'md5key'            =>  "huangsan123tianwang456kangjian78", #商户平台密钥
            'nonce_str'         =>  (string)rand(10000,99999).(string)time(),
            'notify_url'        =>  "",
            'sign'              =>  '',
            'trade_type'        =>  'NATIVE',
            'url'               =>  'https://api.mch.weixin.qq.com/pay/unifiedorder',
        );

        //加载配置
        $this->config($config);
    }

    /**
     * 设置配置
     * @param array $config
     *          string $config['appid'] 开发者编号
     *          string $config['secret'] 开发者密钥
     *          string $config['mch_id'] 微信支付商户号
     *          string $config['md5key'] 微信支付密钥
     *          string $config['notify_url'] 页面通知地址
     */
    public function config($config=array())
    {
        if(gettype($config) && count($config)>0){
            foreach($config as $key=>$val)
            {
                if(array_key_exists($key,$this->config) && gettype($val)=="string" && $val!=''){
                    $this->config[$key]=(string)$val;
                }
            }
        }
        $this->puclicdata=[];
        $this->publicdata();
    }


    /**
     * 发起支付
     * @param number $money 支付的金额以元为单位，精确到分
     * @param string $orderno 订单号
     * @param string $comments 备注
     */
    public function pay($money=0,$orderno='',$comments="消费")
    {
        $wein_data= array(
            'total_fee'             =>  (string)($money*100),
            'out_trade_no'          =>  (string)$orderno,
            'body'                  =>  (string)$comments,
            'spbill_create_ip'      =>  (string)($this->ip()),
        );

        $wein_data= array_merge($wein_data,$this->puclicdata);

        $wein_data['sign']              =   $this->sign($wein_data);

        $xml= $this->simplest_xml_to_array($wein_data);
        if($xml!='') $xml="<xml>\n$xml</xml>";

        $result= $this->curl($this->config['url'],$xml);

        if($result===false)
        {
            return ['type'=>'content','data'=>$this->wenxincmsg('获取订单信息失败')];
        }
        else
        {
            $text= $this->xml_to_array($result);
            
            
            
            //com_log([$text],'wp');
            if($text['return_code']=='FAIL')
            {
                return ['type'=>'content','data'=>$this->wenxincmsg($text['return_msg'])];
            }
            else
            {
                
               if(!isset($text['code_url']) || $text['code_url']=='') 
                   return ['type'=>'content','data'=>$this->wenxincmsg('创建订单失败,请重新支付')]; 
                
                return ['type'=>'qrcode','data'=>$text['code_url']];
            }

        }

    }

    /**
     * 发起退款
     * @param number $money 支付的金额以元为单位，精确到分
     * @param number $refund_money 退款金额 以元为单位，精确到分
     * @param string $orderno 订单号
     */
    public function refund($money=0,$orderno='')
    {
        $wein_data= array(
            'total_fee'             =>  (string)($money*100),
            'refund_fee'            =>  (string)($money*100),
            'out_trade_no'          =>  (string)$orderno,
            'out_refund_no'         =>  (string)$orderno,
            'transaction_id'        => '4001552001201706196513231393',
        );

        $wein_data= array_merge($wein_data,$this->puclicdata);

        $wein_data['sign']              =   $this->sign($wein_data);

        $xml= $this->simplest_xml_to_array($wein_data);
        if($xml!='') $xml="<xml>\n$xml</xml>";

        $result= $this->curl("https://api.mch.weixin.qq.com/secapi/pay/refund",$xml);

        com_log([$result],'wxfund');

        if($result===false)
        {
            return false;
        }
        else
        {
            $text= $this->xml_to_array($result);
            if($text['return_code']=='FAIL')
            {
                return false;
            }
            else
            {
                return ture;
            }

        }

    }

    /**notice**
     *开始通知
     */
    public function notice()
    {
         
        $data=array(
            "code"=>1, #0|成功，1|有错误
            'msg'=>'SUCCESS', #返回给第三方的信息
            'data'=>array(
                'out_trade_no'=>'', #要处理的订单号
                'total_fee'=>0, #用户支付的金额
                'buy_info'=>'', #打款用户的微信呢称
                'pay_data'=>'',#第三方订单号
            ),
        );
        $phpinput='';
        $phpinput= @file_get_contents('php://input');

        if($phpinput!='')
        {
            $notice_data= $this->xml_to_array($phpinput);
        }
        else
        {
            $data['msg']='无交易数据';return $data;

        }
        if(!$notice_data)
        {
            $data['msg']='无交易数据';return $data;
        }

        //com_log([$notice_data],'wx');
        $info='';
        //私钥与签名验证通过
        if($this->notice_check($notice_data))
        {
            if($notice_data['result_code']!='' && $notice_data['out_trade_no']!='' && $notice_data['total_fee']!='')
            {
                //开始业务处理
                if($notice_data['result_code']=='SUCCESS')
                {
                    $data['code'] = 0;
                    $data['data']=array(
                        'out_trade_no'=>$notice_data['out_trade_no'],
                        'total_fee'=>$notice_data['total_fee']/100,
                        'pay_data'=> json_encode($notice_data,JSON_UNESCAPED_UNICODE),
                        //'buy_info'=>$this->buyinfo($notice_data['openid']),

                    );
                }
            }
            else
            {
                $data['msg']='result_code,out_trade_no,total_fee其中一项为空';
            }
        }
        else
        {
            $data['msg']="签名验证失败";
        }

        return $data;
    }

    /**
     * 设置生成加密串需要忽略的参数
     * @param array $Param
     */
    public function filt_Param($Param=[]){
        if(gettype($Param)=="array" && count($Param)>0)
        {
            $this->filtParam=array_merge($this->filtParam,$Param);
        }
    }

    /**
     * 加载支付插件配置
     */
    private function publicdata()
    {
        $this->puclicdata=$this->config;
        unset($this->puclicdata['url']);
        unset($this->puclicdata['md5key']);
        unset($this->puclicdata['secret']);
    }


    /**
     * 通知效验
     * @param array $data
     */
    private function notice_check($data=array())
    {
        $sign= isset($data['sign'])?$data['sign']:'';
        if(isset($data['sign'])) unset($data['sign']);
        return $sign==$this->build_sign($data);
    }

    /**
     * 生成签名
     * @param array $signStr 验证前字符
     * @return 生成的签名串
     */
    private function build_sign($data=array())
    {
        $signStr= $this->bulid_param($data);
        $signStr= "$signStr&key={$this->config['md5key']}";
        $signStr= md5($signStr);
        return strtoupper($signStr);
    }


    /**
     * 组装参数
     * @param array $data 传入的字符
     * @return string 返回值
     */
    private function bulid_param($data=array())
    {
        $str='';
        ksort($data);
        foreach($data as $key=>$val)
        {
            if(gettype($val)!='array' && $val!='')
            {
                if($str!='') $str.="&";
                $str.="$key=$val";
            }
        }
        return $str;
    }


    /**
     * 获取 accessToken
     * @param string $appid
     * @param string $screet
     */
    private function getAccessToken($appid="",$screet="")
    {

        $accessToken=$this->token("");

        //从微信服务端获取$accessToken并缓存
        if($accessToken==''){

            $data= $this->curl("https://api.weixin.qq.com/cgi-bin/token",
                array(
                    'grant_type'=>'client_credential',
                    'appid'=>$appid,
                    'secret'=>$screet)
            );

            $data=json_decode($data,true);
            if(isset($data['access_token']) && isset($data['expires_in'])){
                $accessToken=$this->token($data['access_token'],$data['expires_in']);
            }
        }

        return $accessToken;

    }


    /**
     * 获取打款人头像及呢称
     * @param string $openId
     */
    private function buyinfo($openId='')
    {
        $buyInfo='';

        $accessToken= $this->getAccessToken($this->config['appid'],$this->config['secret']);
        if($accessToken!=''){
            $data=$this->curl("https://api.weixin.qq.com/cgi-bin/user/info",array(
                'access_token' => $accessToken,
                'openid'=>$openId,
            ));
            $data=json_decode($data,true);
            if(isset($data['nickname']) && isset($data['headimgurl'])){
                $buyInfo=$data['nickname'];
            }

        }
        return $buyInfo;

    }
     
    /**
     * 显示错误信息
     * @param string $str
     */
    private function wenxincmsg($str='')
    {
        return $str;
    }


    /**
     * 数组生成XML数据
     * @param array $data
     */
    private function simplest_xml_to_array($data=array()) {
        $str='';
        if(count($data)==0) return '';
        foreach($data as $key=>$val)
        {
            if(gettype($val)=='string' || gettype($val)=='double' )
            {
                $str.="<$key>$val</$key>\n";
            }
            elseif(gettype($val)=='array')
            {
                $str.= "<$key>".$this->simplest_xml_to_array($val)."<$key>";
            }
        }
        return $str;
    }


    /**
     * 远程请求数据
     * @param unknown $url 远程地址
     * @param unknown $data 发送的数组
     * @param number $second 过期时间
     */
    private function  curl($url,$data,$second=30)
    {

        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);//严格校验

        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);

        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        $str= gettype($data)=='array'?http_build_query($data):$data;

        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $str);

        //运行curl
        $str = curl_exec($ch);

        //返回结果
        if($str){
            curl_close($ch);
            return $str;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);

            return "curl出错，错误码:$error";
        }

    }



    //生成签名
    private function sign($data)
    {
        unset($data['sign']);
        return $this->build_sign($data);
    }

    //拆分数组并用 &=链接
    private function postdata($data,$urlencode=false)
    {
        $str='';
        foreach($data as $key => $val)
        {
            if($urlencode) $val=urlencode($val);
            if($str!='') $str.='&'; $str.="$key=$val";
        }
        return $str;
    }

    //请求数据
    private function getpaydata($url,$postdata)
    {
        $params = array(
            'http' => array(
                'method'  => 'POST',
                'header'  => "content-type: application/x-www-form-urlencoded",
                'content' => $postdata,
            )
        );

        return file_get_contents($url, false, stream_context_create($params));
    }


    /**
     * xml转数组
     * @param string $xml 要转换的xml数字符
     */
    private function xml_to_array($xml='')
    {
        libxml_disable_entity_loader(true);

        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

    }


    /**
     * 得到资金信息
     * @param number $money
     * @param string $method
     * @return number|string
     */
    private function  get_money($money = 0, $method = 'less') {
        if (!is_numeric($money)) {
            return 0;
        }

        $money =abs($money) * 100;
        $money=number_format($money,2, '.', '');
        $money = ($method == 'less') ? floor($money) : ceil($money);
        return number_format($money / 100, 2, '.', '');
    }

    /**
     * 获取ip地址
     */
    private function ip()
    {
        $ip=@@$_SERVER["HTTP_X_FORWARDED_FOR"];
        if($ip=='') $ip=$_SERVER["REMOTE_ADDR"];
        return $ip;
    }

    /**
     * 获取微信临时会话token
     * @param string $token
     * @param int $expsec 过期时间
     */
    private function token($token='',$expsec=0)
    {
        $fileName=__dir__."/{$this->cofnig['appid']}wechat_token.txt"; #文件名
        $delimiter="{|}"; #间隔符
        if(!is_file($fileName))
        {
            $result=@file_put_contents($fileName, "");
            if(!$result) exit("token临时文件创建失败，请检查权限");
        }

        if($token!='')
        {
            $result=@file_put_contents($fileName, $token.$delimiter.(time()+60*60));
            if(!$result) exit("token临时文件创建失败，请检查权限");
        }

        $content=  explode($delimiter,file_get_contents($fileName));
        return count($content)>0 && $content[1]>=time()?$content[0]:'';
    }


}
